#ifndef _TASKSYS_H
#define _TASKSYS_H

#include "itasksys.h"

#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <memory>
#include <functional>
#include <future>
#include <queue>
#include <iostream>
#include <unordered_map>

/*
 * TaskSystemSerial: This class is the student's implementation of a
 * serial task execution engine.  See definition of ITaskSystem in
 * itasksys.h for documentation of the ITaskSystem interface.
 */
class TaskSystemSerial: public ITaskSystem {
    public:
        TaskSystemSerial(int num_threads);
        ~TaskSystemSerial();
        const char* name();
        void run(IRunnable* runnable, int num_total_tasks);
        TaskID runAsyncWithDeps(IRunnable* runnable, int num_total_tasks,
                                const std::vector<TaskID>& deps);
        void sync();
};

/*
 * TaskSystemParallelSpawn: This class is the student's implementation of a
 * parallel task execution engine that spawns threads in every run()
 * call.  See definition of ITaskSystem in itasksys.h for documentation
 * of the ITaskSystem interface.
 */
class TaskSystemParallelSpawn: public ITaskSystem {
    public:
        int num_threads;

        TaskSystemParallelSpawn(int num_threads);
        ~TaskSystemParallelSpawn();
        const char* name();
        void run(IRunnable* runnable, int num_total_tasks);
        TaskID runAsyncWithDeps(IRunnable* runnable, int num_total_tasks,
                                const std::vector<TaskID>& deps);
        void sync();
};

/*
 * TaskSystemParallelThreadPoolSpinning: This class is the student's
 * implementation of a parallel task execution engine that uses a
 * thread pool. See definition of ITaskSystem in itasksys.h for
 * documentation of the ITaskSystem interface.
 */
class TaskSystemParallelThreadPoolSpinning: public ITaskSystem {
    public:
        TaskSystemParallelThreadPoolSpinning(int num_threads);
        ~TaskSystemParallelThreadPoolSpinning();
        const char* name();
        void run(IRunnable* runnable, int num_total_tasks);
        TaskID runAsyncWithDeps(IRunnable* runnable, int num_total_tasks,
                                const std::vector<TaskID>& deps);
        void sync();

    public:
        // Enqueue A Task
        // template<typename F, typename ... Args>
        // auto enqueue(F&& f, Args&& ... args) -> std::future<typename std::result_of<F(Args...)>::type>;

        template<typename F, typename ... Args>
        auto enqueue(F&& f, Args&& ... args)
        -> std::future<typename std::result_of<typename std::decay<F>::type(typename std::decay<Args>::type ...)>::type>;

    private:
        // number of threads
        int num_threads;
        // thread pool
        std::vector<std::thread> workers;
        // task queue
        std::queue<std::function<void()>> task_queue;
        // synchronization - spinning lock
        std::mutex queue_mutex;
        // whether to stop the thread pool
        bool stop;
};

struct TaskInfo
{
    TaskID id;
    IRunnable* runnable;
    int num_total_tasks;
    std::vector<TaskID> dependency;

    TaskInfo(TaskID id, IRunnable* runnable, int num_total_tasks, const std::vector<TaskID>& dependency)
    : id(id), runnable(runnable), num_total_tasks(num_total_tasks), dependency(dependency) {}
};

/*
 * TaskSystemParallelThreadPoolSleeping: This class is the student's
 * optimized implementation of a parallel task execution engine that uses
 * a thread pool. See definition of ITaskSystem in
 * itasksys.h for documentation of the ITaskSystem interface.
 */
class TaskSystemParallelThreadPoolSleeping: public ITaskSystem {
    public:
        TaskSystemParallelThreadPoolSleeping(int num_threads);
        ~TaskSystemParallelThreadPoolSleeping();
        const char* name();
        void run(IRunnable* runnable, int num_total_tasks);
        TaskID runAsyncWithDeps(IRunnable* runnable, int num_total_tasks,
                                const std::vector<TaskID>& deps);
        void sync();

    public:
        template<typename F, typename ... Args>
        auto enqueue(F&& f, Args&& ... args) -> 
        std::future<typename std::result_of<
                    typename std::decay<F>::type(
                    typename std::decay<Args>::type ...
                    )>::type>;

        void check_waiting_queue();
    
    private:
    //  Using In Thread Pool
        int num_threads;
        bool stop;
        std::vector<std::thread> workers;
        std::queue<std::function<void()>> task_queue;
        std::mutex queue_mutex;
        std::condition_variable condition;

    //  Sync Manage Waiting queue
        std::mutex waiting_queue_mutex;
        std::mutex completed_mutex;
        std::mutex futures_mutex;
        std::deque<std::pair<TaskID, TaskInfo>> waiting_queue;
        std::atomic<TaskID> id;
        std::vector<std::future<void>> futures; 
    
    public:
        std::unordered_map<TaskID, bool> completed;

};

#endif
